﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Lenic.DI.Lifetimes;

namespace Lenic.DI
{
    /// <summary>
    /// DI 容器扩展方法集合
    /// </summary>
    //[DebuggerStepThrough]
    public static class ContainerExtensions
    {
        #region Register Methods

        /// <summary>
        /// 向 DI 容器中注册 TService 类型的获取委托
        /// </summary>
        /// <typeparam name="TService">委托返回值类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="lifetime">一个 <see cref="ILifetime"/> 类型的实例对象，可以为 <c>null</c>.</param>
        /// <param name="factory">返回值获取委托</param>
        /// <returns>
        /// 传递的 <see cref="Container"/> 类型的实例对象
        /// </returns>
        public static IContainer Register<TService>(this IContainer container, ILifetime lifetime, Func<IContainer, TService> factory)
        {
            return container.Register(Registration<TService>.New(lifetime).Register(factory));
        }

        /// <summary>
        /// 向 DI 容器中注册 TService 类型的获取委托
        /// </summary>
        /// <typeparam name="TService">委托返回值类型</typeparam>
        /// <typeparam name="T">解析需要的参数类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="factory">返回值获取委托</param>
        /// <returns>
        /// 传递的 <see cref="Container"/> 类型的实例对象
        /// </returns>
        public static IContainer Register<TService, T>(this IContainer container, Func<IContainer, T, TService> factory)
        {
            return container.Register(Registration<TService>.New().Register(factory));
        }

        /// <summary>
        /// 向 DI 容器中注册 TService 类型的获取委托
        /// </summary>
        /// <typeparam name="TService">委托返回值类型</typeparam>
        /// <typeparam name="T0">解析需要的第一个参数类型</typeparam>
        /// <typeparam name="T1">解析需要的第二个参数类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="factory">返回值获取委托</param>
        /// <returns>
        /// 传递的 <see cref="Container"/> 类型的实例对象
        /// </returns>
        public static IContainer Register<TService, T0, T1>(this IContainer container, Func<IContainer, T0, T1, TService> factory)
        {
            return container.Register(Registration<TService>.New().Register(factory));
        }

        /// <summary>
        /// 向 DI 容器中注册 TService 类型的获取委托
        /// </summary>
        /// <typeparam name="TService">委托返回值类型</typeparam>
        /// <typeparam name="T0">解析需要的第一个参数类型</typeparam>
        /// <typeparam name="T1">解析需要的第二个参数类型</typeparam>
        /// <typeparam name="T2">解析需要的第三个参数类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="factory">返回值获取委托</param>
        /// <returns>
        /// 传递的 <see cref="Container"/> 类型的实例对象
        /// </returns>
        public static IContainer Register<TService, T0, T1, T2>(this IContainer container, Func<IContainer, T0, T1, T2, TService> factory)
        {
            return container.Register(Registration<TService>.New().Register(factory));
        }

        /// <summary>
        /// 向 DI 容器中注册 TService 类型的获取委托
        /// </summary>
        /// <typeparam name="TService">委托返回值类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="lifetime">一个 <see cref="ILifetime"/> 类型的实例对象，可以为 <c>null</c>.</param>
        /// <param name="name">注册项的命名名称</param>
        /// <param name="factory">返回值获取委托</param>
        /// <returns>传递的 <see cref="Container"/> 类型的实例对象</returns>
        public static IContainer RegisterNamed<TService>(this IContainer container, ILifetime lifetime, string name, Func<IContainer, TService> factory)
        {
            return container.Register(Registration<TService>.New(lifetime).Register(factory).Named(name));
        }

        /// <summary>
        /// 向 DI 容器中注册 TService 类型的获取委托
        /// </summary>
        /// <typeparam name="TService">委托返回值类型</typeparam>
        /// <typeparam name="T">解析需要的参数类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="name">注册项的命名名称</param>
        /// <param name="factory">返回值获取委托</param>
        /// <returns>
        /// 传递的 <see cref="Container"/> 类型的实例对象
        /// </returns>
        public static IContainer RegisterNamed<TService, T>(this IContainer container, string name, Func<IContainer, T, TService> factory)
        {
            return container.Register(Registration<TService>.New().Register(factory).Named(name));
        }

        /// <summary>
        /// 向 DI 容器中注册 TService 类型的获取委托
        /// </summary>
        /// <typeparam name="TService">委托返回值类型</typeparam>
        /// <typeparam name="T0">解析需要的第一个参数类型</typeparam>
        /// <typeparam name="T1">解析需要的第二个参数类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="name">注册项的命名名称</param>
        /// <param name="factory">返回值获取委托</param>
        /// <returns>
        /// 传递的 <see cref="Container"/> 类型的实例对象
        /// </returns>
        public static IContainer RegisterNamed<TService, T0, T1>(this IContainer container, string name, Func<IContainer, T0, T1, TService> factory)
        {
            return container.Register(Registration<TService>.New().Register(factory).Named(name));
        }

        /// <summary>
        /// 向 DI 容器中注册 TService 类型的获取委托
        /// </summary>
        /// <typeparam name="TService">委托返回值类型</typeparam>
        /// <typeparam name="T0">解析需要的第一个参数类型</typeparam>
        /// <typeparam name="T1">解析需要的第二个参数类型</typeparam>
        /// <typeparam name="T2">解析需要的第三个参数类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="name">注册项的命名名称</param>
        /// <param name="factory">返回值获取委托</param>
        /// <returns>
        /// 传递的 <see cref="Container"/> 类型的实例对象
        /// </returns>
        public static IContainer RegisterNamed<TService, T0, T1, T2>(this IContainer container, string name, Func<IContainer, T0, T1, T2, TService> factory)
        {
            return container.Register(Registration<TService>.New().Register(factory).Named(name));
        }

        #endregion Register Methods

        #region Resolve Methods

        /// <summary>
        /// 从容器中解析指定的类型实例对象列表
        /// </summary>
        /// <typeparam name="TService">待解析的类型</typeparam>
        /// <param name="container">一个 <see cref="IContainer"/> 类型的实例对象</param>
        /// <param name="name">解析类型的命名名称</param>
        /// <returns>
        /// 解析的实例对象列表
        /// </returns>
        public static IEnumerable<TService> ResolveMulti<TService>(this IContainer container)
        {
            var data = container.ResolveLogic(typeof(TService));

            foreach (var item in data)
            {
                yield return item.GetInstance(null, p => (p as Func<IContainer, TService>)(container));
            }
        }

        /// <summary>
        /// 从容器中解析指定的类型实例对象
        /// </summary>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <typeparam name="TService">待解析的类型</typeparam>
        /// <returns>解析的实例对象</returns>
        public static TService Resolve<TService>(this IContainer container)
        {
            return container.ResolveNamed<TService>(null);
        }

        /// <summary>
        /// 从容器中解析指定的类型实例对象
        /// </summary>
        /// <typeparam name="TService">待解析的类型</typeparam>
        /// <typeparam name="T">解析需要的参数类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="arg">解析需要的参数值</param>
        /// <returns>
        /// 解析的实例对象
        /// </returns>
        public static TService Resolve<TService, T>(this IContainer container, T arg)
        {
            return container.ResolveNamed<TService, T>(null, arg);
        }

        /// <summary>
        /// 从容器中解析指定的类型实例对象
        /// </summary>
        /// <typeparam name="TService">待解析的类型</typeparam>
        /// <typeparam name="T0">解析需要的第一个参数类型</typeparam>
        /// <typeparam name="T1">解析需要的第二个参数类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="arg0">解析需要的第一个参数值</param>
        /// <param name="arg1">解析需要的第二个参数值</param>
        /// <returns>
        /// 解析的实例对象
        /// </returns>
        public static TService Resolve<TService, T0, T1>(this IContainer container, T0 arg0, T1 arg1)
        {
            return container.ResolveNamed<TService, T0, T1>(null, arg0, arg1);
        }

        /// <summary>
        /// 从容器中解析指定的类型实例对象
        /// </summary>
        /// <typeparam name="TService">待解析的类型</typeparam>
        /// <typeparam name="T0">解析需要的第一个参数类型</typeparam>
        /// <typeparam name="T1">解析需要的第二个参数类型</typeparam>
        /// <typeparam name="T2">解析需要的第三个参数类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="arg0">解析需要的第一个参数值</param>
        /// <param name="arg1">解析需要的第二个参数值</param>
        /// <param name="arg2">解析需要的第三个参数值</param>
        /// <returns>
        /// 解析的实例对象
        /// </returns>
        public static TService Resolve<TService, T0, T1, T2>(this IContainer container, T0 arg0, T1 arg1, T2 arg2)
        {
            return container.ResolveNamed<TService, T0, T1, T2>(null, arg0, arg1, arg2);
        }

        /// <summary>
        /// 从容器中解析指定的类型实例对象
        /// </summary>
        /// <typeparam name="TService">待解析的类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="name">解析类型的命名名称</param>
        /// <returns>
        /// 解析的实例对象
        /// </returns>
        public static TService ResolveNamed<TService>(this IContainer container, string name)
        {
            var value = container.ResolveLogic(typeof(TService), name);

            return value.GetInstance(name, p => (p as Func<IContainer, TService>)(container));
        }

        /// <summary>
        /// 从容器中解析指定的类型实例对象
        /// </summary>
        /// <typeparam name="TService">待解析的类型</typeparam>
        /// <typeparam name="T">解析需要的参数类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="name">解析类型的命名名称</param>
        /// <param name="arg">解析需要的参数值</param>
        /// <returns>
        /// 解析的实例对象
        /// </returns>
        public static TService ResolveNamed<TService, T>(this IContainer container, string name, T arg)
        {
            var value = container.ResolveLogic(typeof(TService), name);

            return value.GetInstance(name, p => (p as Func<IContainer, T, TService>)(container, arg));
        }

        /// <summary>
        /// 从容器中解析指定的类型实例对象
        /// </summary>
        /// <typeparam name="TService">待解析的类型</typeparam>
        /// <typeparam name="T0">解析需要的第一个参数类型</typeparam>
        /// <typeparam name="T1">解析需要的第二个参数类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="name">解析类型的命名名称</param>
        /// <param name="arg0">解析需要的第一个参数值</param>
        /// <param name="arg1">解析需要的第二个参数值</param>
        /// <returns>
        /// 解析的实例对象
        /// </returns>
        public static TService ResolveNamed<TService, T0, T1>(this IContainer container, string name, T0 arg0, T1 arg1)
        {
            var value = container.ResolveLogic(typeof(TService), name);

            return value.GetInstance(name, p => (p as Func<IContainer, T0, T1, TService>)(container, arg0, arg1));
        }

        /// <summary>
        /// 从容器中解析指定的类型实例对象
        /// </summary>
        /// <typeparam name="TService">待解析的类型</typeparam>
        /// <typeparam name="T0">解析需要的第一个参数类型</typeparam>
        /// <typeparam name="T1">解析需要的第二个参数类型</typeparam>
        /// <typeparam name="T2">解析需要的第三个参数类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="name">解析类型的命名名称</param>
        /// <param name="arg0">解析需要的第一个参数值</param>
        /// <param name="arg1">解析需要的第二个参数值</param>
        /// <param name="arg2">解析需要的第三个参数值</param>
        /// <returns>
        /// 解析的实例对象
        /// </returns>
        public static TService ResolveNamed<TService, T0, T1, T2>(this IContainer container, string name, T0 arg0, T1 arg1, T2 arg2)
        {
            var value = container.ResolveLogic(typeof(TService), name);

            return value.GetInstance(name, p => (p as Func<IContainer, T0, T1, T2, TService>)(container, arg0, arg1, arg2));
        }

        #endregion Resolve Methods

        #region Registration<TService> Register

        /// <summary>
        /// 为当前注册项添加获取委托
        /// </summary>
        /// <typeparam name="TService">委托返回值类型</typeparam>
        /// <param name="obj">一个注册项的实例.</param>
        /// <param name="factory">返回值获取委托</param>
        /// <returns>修改后的自身</returns>
        public static Registration<TService> Register<TService>(this Registration<TService> obj, Func<IContainer, TService> factory)
        {
            return obj.Register(factory);
        }

        /// <summary>
        /// 为当前注册项添加获取委托
        /// </summary>
        /// <typeparam name="T">解析需要的参数类型</typeparam>
        /// <typeparam name="TService">委托返回值类型</typeparam>
        /// <param name="obj">一个注册项的实例.</param>
        /// <param name="factory">返回值获取委托</param>
        /// <returns>修改后的自身</returns>
        public static Registration<TService> Register<T, TService>(this Registration<TService> obj, Func<IContainer, T, TService> factory)
        {
            return obj.Register(factory);
        }

        /// <summary>
        /// 为当前注册项添加获取委托
        /// </summary>
        /// <typeparam name="T0">解析需要的第一个参数类型</typeparam>
        /// <typeparam name="T1">解析需要的第二个参数类型</typeparam>
        /// <typeparam name="TService">委托返回值类型</typeparam>
        /// <param name="obj">一个注册项的实例.</param>
        /// <param name="factory">返回值获取委托</param>
        /// <returns>修改后的自身</returns>
        public static Registration<TService> Register<T0, T1, TService>(this Registration<TService> obj, Func<IContainer, T0, T1, TService> factory)
        {
            return obj.Register(factory);
        }

        /// <summary>
        /// 为当前注册项添加获取委托
        /// </summary>
        /// <typeparam name="T0">解析需要的第一个参数类型</typeparam>
        /// <typeparam name="T1">解析需要的第二个参数类型</typeparam>
        /// <typeparam name="T2">解析需要的第三个参数类型</typeparam>
        /// <typeparam name="TService">委托返回值类型</typeparam>
        /// <param name="obj">一个注册项的实例.</param>
        /// <param name="factory">返回值获取委托</param>
        /// <returns>修改后的自身</returns>
        public static Registration<TService> Register<T0, T1, T2, TService>(this Registration<TService> obj, Func<IContainer, T0, T1, T2, TService> factory)
        {
            return obj.Register(factory);
        }

        #endregion Registration<TService> Register

        #region Other Extender

        /// <summary>
        /// 获取一个值, 通过该值指示容器中是否包含指定类型指定名称的项.
        /// </summary>
        /// <typeparam name="T">待解析的目标类型</typeparam>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="name">解析类型的命名名称</param>
        /// <returns>
        ///   <c>true</c> 表示包含; 否则返回 <c>false</c>.
        /// </returns>
        public static bool Contains<T>(this IContainer container, string name = null)
        {
            ILifetime value = null;
            return container.TryResolve(typeof(T), name, out value);
        }

        /// <summary>
        /// 解析的具体实现
        /// </summary>
        /// <param name="container">一个 <see cref="Container"/> 类型的实例对象</param>
        /// <param name="serviceType">待解析的类型</param>
        /// <param name="name">解析类型的命名名称</param>
        /// <returns>解析的实例对象</returns>
        public static ILifetime ResolveLogic(this IContainer container, Type serviceType, string name)
        {
            ILifetime value = null;
            if (!container.TryResolve(serviceType, name, out value))
                throw new ResolveException(serviceType, name);

            return value;
        }

        /// <summary>
        /// 解析的具体实现
        /// </summary>
        /// <param name="container">一个 <see cref="IContainer"/> 类型的实例对象</param>
        /// <param name="serviceType">待解析的类型</param>
        /// <returns>解析的实例对象数据</returns>
        public static ILifetime[] ResolveLogic(this IContainer container, Type serviceType)
        {
            ILifetime[] data = null;
            if (!container.TryResolve(serviceType, out data))
                throw new ResolveException(serviceType, null);

            return data;
        }

        /// <summary>
        /// 获取缺省的容器未注册类型注册方法.
        /// </summary>
        /// <returns>未注册类型注册方法.</returns>
        public static Register DefaultRegister()
        {
            return delegate(IContainer container, Type serviceType, string name, ref ILifetime lifetime)
            {
                if (lifetime != null || serviceType.IsAbstract)
                    return;

                GenericRegisterLogic(container, serviceType, name, (lifetime = new DefaultLifetime()), serviceType);
            };
        }

        /// <summary>
        /// 获取默认接口实现的容器未注册类型注册方法.
        /// </summary>
        /// <param name="getRealTypeFunction">由接口类型获取实现类型时的方法：
        /// <c>null</c> 表示在 AppDomain.CurrentDomain.GetAssemblies() 中使用 p.Name.Substring(1) 查找.</param>
        /// <returns>未注册类型注册方法.</returns>
        public static Register InterfaceRegister(Func<Type, Type> getRealTypeFunction = null)
        {
            return delegate(IContainer container, Type serviceType, string name, ref ILifetime lifetime)
            {
                if (lifetime != null || !serviceType.IsInterface)
                    return;

                Type realType = null;
                if (getRealTypeFunction == null)
                {
                    var realTypeName = serviceType.Name.Substring(1);

                    realType = AppDomain.CurrentDomain.GetAssemblies()
                        .SelectMany(r => r.GetTypes()).FirstOrDefault(r => r.Name == realTypeName);
                }
                else
                    realType = getRealTypeFunction(serviceType);

                if (realType == null)
                    throw new ResolveException(serviceType, name);

                GenericRegisterLogic(container, serviceType, name, (lifetime = new DefaultLifetime()), realType);

                InterfaceRegisterLogic(container, serviceType, name, ref lifetime, getRealTypeFunction);
            };
        }

        /// <summary>
        /// 获取泛型类型的容器未注册类型注册方法.
        /// </summary>
        /// <param name="genericInterfaceDefinition">泛型接口类型.</param>
        /// <param name="genericRealTypeDefinition">泛型实现类型.</param>
        /// <param name="func">对象生存周期的创建委托.</param>
        /// <returns>
        /// 未注册类型注册方法.
        /// </returns>
        public static Register GenericRegister(Type genericInterfaceDefinition, Type genericRealTypeDefinition, Func<ILifetime> func = null)
        {
            if (func == null)
                func = () => new DefaultLifetime();

            return delegate(IContainer container, Type serviceType, string name, ref ILifetime lifetime)
            {
                if (lifetime != null ||
                    !serviceType.IsGenericType ||
                    (serviceType.GetGenericTypeDefinition() != genericInterfaceDefinition))
                    return;

                var realType = genericRealTypeDefinition.MakeGenericType(serviceType.GetGenericArguments());

                GenericRegisterLogic(container, serviceType, name, lifetime = func(), realType);
            };
        }

        #endregion Other Extender

        #region Private Methods

        private static void DefaultRegisterLogic(IContainer container, Type serviceType, string name, ref ILifetime lifetime)
        {
        }

        private static void InterfaceRegisterLogic(IContainer container, Type serviceType, string name, ref ILifetime lifetime, Func<Type, Type> realTypeFunc)
        {
        }

        private static void GenericRegisterLogic(IContainer container, Type serviceType, string name, ILifetime lifetime, Type realType)
        {
            // Find the most parameters constructor
            ConstructorInfoX item = item = realType.GetConstructors()
                    .Select(p => new ConstructorInfoX { Constructor = p, Parameters = p.GetParameters() })
                    .Where(p => !p.Parameters.Any(r => r.ParameterType.IsPointer || r.IsOut || r.ParameterType.IsByRef))
                    .OrderByDescending(p => p.Parameters.Length)
                    .FirstOrDefault();
            if (item == null)
            {
                if (realType.IsValueType)
                    item = new ConstructorInfoX { Parameters = new ParameterInfo[0] };
                else
                    throw new ResolveException(serviceType, name);
            }

            // Registration<T>.New(new DefaultLifetime())
            var targetType = typeof(Registration<>).MakeGenericType(serviceType);
            var targetMethod = targetType.GetMethod("New", BindingFlags.Static | BindingFlags.Public);
            var callExpr = Expression.Call(targetMethod, Expression.Constant(lifetime, typeof(ILifetime)));

            // c => New T(c.Resolve<ParameterType1>(), c.Resolve<ParameterType2>())
            var cp = Expression.Parameter(typeof(IContainer), "c");
            var parameters = item.Parameters.Select(p => Expression.Call(
                typeof(ContainerExtensions).GetMethod("Resolve", new Type[] { typeof(IContainer) })
                    .MakeGenericMethod(p.ParameterType.IsByRef ? p.ParameterType.GetElementType() : p.ParameterType),
                cp))
                .ToArray();
            NewExpression newExpr = null;
            if (item.Constructor == null)
                newExpr = Expression.New(realType); // ValueType
            else
                newExpr = Expression.New(item.Constructor, parameters); // Class Or Other
            var lambda = Expression.Lambda(newExpr, cp);

            // Registration<T>.New(new DefaultLifetime()).Register(Delegate)
            Delegate @delegate = null;
            if (lambda.Body.Type == serviceType)
                @delegate = lambda.Compile();
            else
                @delegate = Expression.Lambda(Expression.Convert(lambda.Body, serviceType), lambda.Parameters.ToArray()).Compile();
            var registerExpr = Expression.Call(callExpr, "Register", null, Expression.Constant(@delegate));

            // Registration<T>.New(new DefaultLifetime()).Register(Delegate).Named(name)
            var namedExpr = Expression.Call(registerExpr, "Named", null, Expression.Constant(name, typeof(string)));

            // Fetch returned value
            var obj = Expression.Lambda<Func<object>>(namedExpr).Compile()();

            // IContainer.Register(Registration<T>)
            var register = container.GetType().GetMethod("Register");
            register = register.MakeGenericMethod(serviceType);
            register.Invoke(container, new object[] { obj });
        }

        private class ConstructorInfoX
        {
            public ConstructorInfo Constructor { get; set; }

            public ParameterInfo[] Parameters { get; set; }
        }

        #endregion Private Methods
    }
}